Synergetics
Oregon Curriculum Network

Computing Volumes in XYZ and IVM units

by Kirby Urner, July 2016

A cube is composed of 24 identical not-regular tetrahedrons, each with a corner at the cube's center, an edge from cube's center to a face center, and two more to adjacent cube corners on that face, defining six edges in all (Fig. 1).

If we define the cube's edges to be √2 then the whole cube would have volume √2 √2 √2 in XYZ units.

However, in IVM units, the very same cube has a volume of 3, owing to the differently-shaped volume unit, a tetrahedron of edges 2, inscribed in this same cube. Fig. 986.210 from Synergetics:

Those lengths would be in R-units, where R is the radius of a unit sphere. In D-units, twice as long (D = 2R), the tetrahedron has edges 1 and the cube has edges √2/2.

By XYZ we mean the XYZ coordinate system of René Descartes (1596 – 1650).

By IVM we mean the "octet-truss", a space-frame consisting of tetrahedrons and octahedrons in a space-filling matrix, with twice as many tetrahedrons as octahedrons.

The tetrahedron and octahedron have relative volumes of 1:4. The question then becomes, how to superimpose the two.

The canonical solution is to start with unit-radius balls (spheres) of radius R. R = 1 in other words, whereas D, the diameter, is 2. Alternatively, we may set D = 1 and R = 0.5, keeping the same 2:1 ratio for D:R.

The XYZ cube has edges R, whereas the IVM tetrahedron has edges D. That relative sizing convention brings their respective volumes fairly close together, with the cube's volume exceeding the tetrahedron's by about six percent.


In [1]:
import math
xyz_volume = math.sqrt(2)**3
ivm_volume = 3
print("XYZ units:", xyz_volume)
print("IVM units:", ivm_volume)
print("Conversion constant:", ivm_volume/xyz_volume)


XYZ units: 2.8284271247461907
IVM units: 3
Conversion constant: 1.060660171779821

The Python code below encodes a Tetrahedron type based solely on its six edge lengths. The code makes no attempt to determine the consequent angles.

A complicated volume formula, mined from the history books and streamlined by mathematician Gerald de Jong, outputs the volume of said tetrahedron in both IVM and XYZ units.

The unittests that follow assure it's producing the expected results. The formula bears great resemblance to the one by Piero della Francesca.


In [2]:
from math import sqrt as rt2
from qrays import Qvector, Vector

R =0.5
D =1.0

S3    = pow(9/8, 0.5)
root2 = rt2(2)
root3 = rt2(3)
root5 = rt2(5)
root6 = rt2(6)
PHI = (1 + root5)/2.0

class Tetrahedron:
    """
    Takes six edges of tetrahedron with faces
    (a,b,d)(b,c,e)(c,a,f)(d,e,f) -- returns volume
    in ivm and xyz units
    """

    def __init__(self, a,b,c,d,e,f):
        self.a, self.a2 = a, a**2
        self.b, self.b2 = b, b**2
        self.c, self.c2 = c, c**2
        self.d, self.d2 = d, d**2
        self.e, self.e2 = e, e**2
        self.f, self.f2 = f, f**2

    def ivm_volume(self):
        ivmvol = ((self._addopen() - self._addclosed() - self._addopposite())/2) ** 0.5
        return ivmvol

    def xyz_volume(self):
        xyzvol = rt2(8/9) * self.ivm_volume()
        return xyzvol

    def _addopen(self):
        a2,b2,c2,d2,e2,f2 = self.a2, self.b2, self.c2, self.d2, self.e2, self.f2
        sumval = f2*a2*b2
        sumval +=  d2 * a2 * c2
        sumval +=  a2 * b2 * e2
        sumval +=  c2 * b2 * d2
        sumval +=  e2 * c2 * a2
        sumval +=  f2 * c2 * b2
        sumval +=  e2 * d2 * a2
        sumval +=  b2 * d2 * f2
        sumval +=  b2 * e2 * f2
        sumval +=  d2 * e2 * c2
        sumval +=  a2 * f2 * e2
        sumval +=  d2 * f2 * c2
        return sumval

    def _addclosed(self):
        a2,b2,c2,d2,e2,f2 = self.a2, self.b2, self.c2, self.d2, self.e2, self.f2
        sumval =   a2 * b2 * d2
        sumval +=  d2 * e2 * f2
        sumval +=  b2 * c2 * e2
        sumval +=  a2 * c2 * f2
        return sumval

    def _addopposite(self):
        a2,b2,c2,d2,e2,f2 = self.a2, self.b2, self.c2, self.d2, self.e2, self.f2
        sumval =  a2 * e2 * (a2 + e2)
        sumval += b2 * f2 * (b2 + f2)
        sumval += c2 * d2 * (c2 + d2)
        return sumval
    
def make_tet(v0,v1,v2):
    """
    three edges from any corner, remaining three edges computed
    """
    tet = Tetrahedron(v0.length(), v1.length(), v2.length(), 
                      (v0-v1).length(), (v1-v2).length(), (v2-v0).length())
    return tet.ivm_volume(), tet.xyz_volume()

tet = Tetrahedron(D, D, D, D, D, D)
print(tet.ivm_volume())


1.0

The make_tet function takes three vectors from a common corner, in terms of vectors with coordinates, and computes the remaining missing lengths, thereby getting the information it needs to use the Tetrahedron class as before.


In [3]:
import unittest
from qrays import Vector, Qvector

class Test_Tetrahedron(unittest.TestCase):

    def test_unit_volume(self):
        tet = Tetrahedron(D, D, D, D, D, D)
        self.assertEqual(tet.ivm_volume(), 1, "Volume not 1")

    def test_e_module(self):
        e0 = D
        e1 = root3 * PHI**-1
        e2 = rt2((5 - root5)/2)
        e3 = (3 - root5)/2
        e4 = rt2(5 - 2*root5)
        e5 = 1/PHI
        tet = Tetrahedron(e0, e1, e2, e3, e4, e5)
        self.assertTrue(1/23 > tet.ivm_volume()/8 > 1/24, "Wrong E-mod")
        
    def test_unit_volume2(self):
        tet = Tetrahedron(R, R, R, R, R, R)
        self.assertAlmostEqual(float(tet.xyz_volume()), 0.117851130)

    def test_phi_edge_tetra(self):
        tet = Tetrahedron(D, D, D, D, D, PHI)
        self.assertAlmostEqual(float(tet.ivm_volume()), 0.70710678)

    def test_right_tetra(self):
        e = pow((root3/2)**2 + (root3/2)**2, 0.5)  # right tetrahedron
        tet = Tetrahedron(D, D, D, D, D, e)
        self.assertAlmostEqual(tet.xyz_volume(), 1)

    def test_quadrant(self):
        qA = Qvector((1,0,0,0))
        qB = Qvector((0,1,0,0))
        qC = Qvector((0,0,1,0))
        tet = make_tet(qA, qB, qC) 
        self.assertAlmostEqual(tet[0], 0.25) 

    def test_octant(self):
        x = Vector((0.5, 0,   0))
        y = Vector((0  , 0.5, 0))
        z = Vector((0  , 0  , 0.5))
        tet = make_tet(x,y,z)
        self.assertAlmostEqual(tet[1], 1/6, 5) # good to 5 places

    def test_quarter_octahedron(self):
        a = Vector((1,0,0))
        b = Vector((0,1,0))
        c = Vector((0.5,0.5,root2/2))
        tet = make_tet(a, b, c)
        self.assertAlmostEqual(tet[0], 1, 5) # good to 5 places  

    def test_xyz_cube(self):
        a = Vector((0.5, 0.0, 0.0))
        b = Vector((0.0, 0.5, 0.0))
        c = Vector((0.0, 0.0, 0.5))
        R_octa = make_tet(a,b,c) 
        self.assertAlmostEqual(6 * R_octa[1], 1, 4) # good to 4 places  

    def test_s3(self):
        D_tet = Tetrahedron(D, D, D, D, D, D)
        a = Vector((0.5, 0.0, 0.0))
        b = Vector((0.0, 0.5, 0.0))
        c = Vector((0.0, 0.0, 0.5))
        R_cube = 6 * make_tet(a,b,c)[1]
        self.assertAlmostEqual(D_tet.xyz_volume() * S3, R_cube, 4)

    def test_martian(self):
        p = Qvector((2,1,0,1))
        q = Qvector((2,1,1,0))
        r = Qvector((2,0,1,1))
        result = make_tet(5*q, 2*p, 2*r)
        self.assertAlmostEqual(result[0], 20, 7)
        
    def test_phi_tet(self):
        "edges from common vertex: phi, 1/phi, 1"
        p = Vector((1, 0, 0))
        q = Vector((1, 0, 0)).rotz(60) * PHI
        r = Vector((0.5, root3/6, root6/3)) * 1/PHI
        result = make_tet(p, q, r)
        self.assertAlmostEqual(result[0], 1, 7)
        
    def test_phi_tet_2(self):
        p = Qvector((2,1,0,1))
        q = Qvector((2,1,1,0))
        r = Qvector((2,0,1,1))
        result = make_tet(PHI*q, (1/PHI)*p, r)
        self.assertAlmostEqual(result[0], 1, 7)
        
    def test_phi_tet_3(self):
        T = Tetrahedron(PHI, 1/PHI, 1.0, 
                        root2, root2/PHI, root2)
        result = T.ivm_volume()
        self.assertAlmostEqual(result, 1, 7)

    def test_koski(self):
        a = 1 
        b = PHI ** -1
        c = PHI ** -2
        d = (root2) * PHI ** -1 
        e = (root2) * PHI ** -2
        f = (root2) * PHI ** -1       
        T = Tetrahedron(a,b,c,d,e,f)
        result = T.ivm_volume()
        self.assertAlmostEqual(result, PHI ** -3, 7)  
        
a = Test_Tetrahedron()

R =0.5
D =1.0

suite = unittest.TestLoader().loadTestsFromModule(a)
unittest.TextTestRunner().run(suite)


...............
----------------------------------------------------------------------
Ran 15 tests in 0.025s

OK
Out[3]:
<unittest.runner.TextTestResult run=15 errors=0 failures=0>

The above tetrahedron has a=2, b=2, c=5, for a volume of 20. The remaining three lengths have not been computed as it's sufficient to know only a, b, c if the angles between them are those of the regular tetrahedron.

That's how IVM volume is computed: multiply a b c from a regular tetrahedron corner, then "close the lid" to see the volume.


In [4]:
a = 2
b = 4
c = 5
d = 3.4641016151377544
e = 4.58257569495584
f = 4.358898943540673
tetra = Tetrahedron(a,b,c,d,e,f)
print("IVM volume of tetra:", round(tetra.ivm_volume(),5))


IVM volume of tetra: 40.0

Lets define a MITE, one of these 24 identical space-filling tetrahedrons, with reference to D=1, R=0.5, as this is how our Tetrahedron class is calibrated. The cubes 12 edges will all be √2/2.

Edges 'a' 'b' 'c' fan out from the cube center, with 'b' going up to a face center, with 'a' and 'c' to adjacent ends of the face's edge.

From the cube's center to mid-face is √2/4 (half an edge), our 'b'. 'a' and 'c' are both half the cube's body diagonal of √(3/2)/2 or √(3/8).

Edges 'd', 'e' and 'f' define the facet opposite the cube's center.

'd' and 'e' are both half face diagonals or 0.5, whereas 'f' is a cube edge, √2/2. This gives us our tetrahedron:


In [5]:
b = rt2(2)/4
a = c = rt2(3/8)
d = e = 0.5
f = rt2(2)/2
mite = Tetrahedron(a, b, c, d, e, f)
print("IVM volume of Mite:", round(mite.ivm_volume(),5))
print("XYZ volume of Mite:", round(mite.xyz_volume(),5))


IVM volume of Mite: 0.125
XYZ volume of Mite: 0.11785

Allowing for floating point error, this space-filling right tetrahedron has a volume of 0.125 or 1/8. Since 24 of them form a cube, said cube has a volume of 3. The XYZ volume, on the other hand, is what we'd expect from a regular tetrahedron of edges 0.5 in the current calibration system.


In [6]:
regular = Tetrahedron(0.5, 0.5, 0.5, 0.5, 0.5, 0.5)
print("MITE volume in XYZ units:", round(regular.xyz_volume(),5))
print("XYZ volume of 24-Mite Cube:", round(24 * regular.xyz_volume(),5))


MITE volume in XYZ units: 0.11785
XYZ volume of 24-Mite Cube: 2.82843

The MITE (minimum tetrahedron) further dissects into component modules, a left and right A module, then either a left or right B module. Outwardly, the positive and negative MITEs look the same. Here are some drawings from R. Buckminster Fuller's research, the chief popularizer of the A and B modules.

In a different Jupyter Notebook, we could run these tetrahedra through our volume computer to discover both As and Bs have a volume of 1/24 in IVM units.

Instead, lets take a look at the E-module and compute its volume.


The black hub is at the center of the RT, as shown here...


RT center is the black hub (Koski with vZome)

In [7]:
from math import sqrt as rt2
from tetravolume import make_tet, Vector

ø = (rt2(5)+1)/2
e0 = Black_Yellow = rt2(3)*ø**-1
e1 = Black_Blue = 1
e3 = Yellow_Blue = (3 - rt2(5))/2
e6 = Black_Red = rt2((5 - rt2(5))/2)
e7 = Blue_Red = 1/ø

# E-mod is a right tetrahedron, so xyz is easy
v0 = Vector((Black_Blue, 0, 0))
v1 = Vector((Black_Blue, Yellow_Blue, 0))
v2 = Vector((Black_Blue, 0, Blue_Red))

# assumes R=0.5 so computed result is 8x needed
# volume, ergo divide by 8.
ivm, xyz = make_tet(v0,v1,v2)

print("IVM volume:", round(ivm/8, 5))
print("XYZ volume:", round(xyz/8, 5))


IVM volume: 0.04173
XYZ volume: 0.03934

This information is being shared around Portland in various contexts. Below, an image from a hands-on workshop in 2010 organized by the Portland Free School.